﻿/*************************************************************
 *页式管理相关代码 add by visual 2016.4.19
 **************************************************************/

#include "type.h"
#include "const.h"
#include "protect.h"
#include "string.h"
#include "proc.h"
#include "global.h"
#include "proto.h"
#include "memman.h"
#include "stdio.h"

// to determine if a page fault is reparable. added by xw, 18/6/11
u32 cr2_save;
u32 cr2_count = 0;

/*======================================================================*
			   switch_pde			added by xw, 17/12/11
 *switch the page directory table after schedule() is called
 *======================================================================*/
void switch_pde()
{
	cr3_ready = p_proc_current->task.cr3;
}

/*======================================================================*
			   init_page_pte		add by visual 2016.4.19
*该函数只初始化了进程的高端（内核端）地址页表
 *======================================================================*/
u32 init_page_pte(u32 pid)
{ //页表初始化函数

	u32 AddrLin, pde_addr_phy_temp, err_temp;

	pde_addr_phy_temp = (u32)do_kmalloc_4k();			 //为页目录申请一页
	memset((void *)K_PHY2LIN(pde_addr_phy_temp), 0, num_4K); // add by visual 2016.5.26

	if (pde_addr_phy_temp < 0 || (pde_addr_phy_temp & 0x3FF) != 0) // add by visual 2016.5.9
	{
		vga_write_str_color("init_page_pte Error:pde_addr_phy_temp", 0x74);
		return -1;
	}

	proc_table[pid].task.cr3 = pde_addr_phy_temp; //初始化了进程表中cr3寄存器变量，属性位暂时不管
	/*********************页表初始化部分*********************************/
	u32 phy_addr = 0;

	for (AddrLin = KernelLinBase, phy_addr = 0; AddrLin < KernelLinBase + KernelSize; AddrLin += num_4K, phy_addr += num_4K)
	{							    //只初始化内核部分，3G后的线性地址映射到物理地址开始处
		err_temp = lin_mapping_phy(AddrLin,		    //线性地址					//add by visual 2016.5.9
					   phy_addr,		    //物理地址
					   pid,			    //进程pid						//edit by visual 2016.5.19
					   PG_P | PG_USU | PG_RWW,  //页目录的属性位（用户权限）			//edit by visual 2016.5.26
					   PG_P | PG_USS | PG_RWW); //页表的属性位（系统权限）				//edit by visual 2016.5.17
		if (err_temp != 0)
		{
			vga_write_str_color("init_page_pte Error:lin_mapping_phy", 0x74);
			return -1;
		}
	}

	return 0;
}

/*======================================================================*
			   page_fault_handle		edit by visual 2016.5.9
 *======================================================================*/
void page_fault_handler(u32 vec_no,   //异常编号，此时应该是14，代表缺页异常
			u32 err_code, //错误码
			u32 eip,	     //导致缺页的指令的线性地址
			u32 cs,	     //发生错误时的代码段寄存器内容
			u32 eflags)   //时发生错误的标志寄存器内容
{					     //缺页中断处理函数
	u32 pde_addr_phy_temp;
	u32 pte_addr_phy_temp;
	u32 cr2;

	cr2 = read_cr2();

	// if page fault happens in kernel, it's an error.
	if (kernel_initial == 1)
	{
		kprintf("\n");
		vga_write_str_color("Page Fault\n", 0x74);
		vga_write_str_color("eip=", 0x74); //灰底红字
		kprintf("%d", eip);
		vga_write_str_color("eflags=", 0x74);
		kprintf("%d", eflags);
		vga_write_str_color("cs=", 0x74);
		kprintf("%d", cs);
		vga_write_str_color("err_code=", 0x74);
		kprintf("%d", err_code);
		vga_write_str_color("Cr2=", 0x74); //灰底红字
		kprintf("%d", cr2);
		halt();
	}

	//获取该进程页目录物理地址
	pde_addr_phy_temp = get_pde_phy_addr(p_proc_current->task.pid);
	//获取该线性地址对应的页表的物理地址
	pte_addr_phy_temp = get_pte_phy_addr(p_proc_current->task.pid, cr2);

	if (cr2 == cr2_save)
	{
		cr2_count++;
		if (cr2_count == 5)
		{
			vga_set_disppos(0);
			kprintf("\n");
			vga_write_str_color("Page Fault\n", 0x74);
			vga_write_str_color("eip=", 0x74); //灰底红字
			kprintf("%x", eip);
			vga_write_str_color("eflags=", 0x74);
			kprintf("%x", eflags);
			vga_write_str_color("cs=", 0x74);
			kprintf("%x", cs);
			vga_write_str_color("err_code=", 0x74);
			kprintf("%x", err_code);
			vga_write_str_color("Cr2=", 0x74); //灰底红字
			kprintf("%x", cr2);
			vga_write_str_color("Cr3=", 0x74);
			kprintf("%x", p_proc_current->task.cr3);
			//获取页目录中填写的内容
			vga_write_str_color("Pde=", 0x74);
			kprintf("%x", *((u32 *)K_PHY2LIN(pde_addr_phy_temp) + get_pde_index(cr2)));
			//获取页表中填写的内容
			vga_write_str_color("Pte=", 0x74);
			kprintf("%x", *((u32 *)K_PHY2LIN(pte_addr_phy_temp) + get_pte_index(cr2)));
			asm volatile("cli");
			halt();
		}
	}
	else
	{
		cr2_save = cr2;
		cr2_count = 0;
	}

	if (0 == pte_exist(pde_addr_phy_temp, cr2))
	{ //页表不存在
		// vga_write_str_color("[Pde Fault!]",0x74);	//灰底红字
		(*((u32 *)K_PHY2LIN(pde_addr_phy_temp) + get_pde_index(cr2))) |= PG_P;
		// vga_write_str_color("[Solved]",0x74);
	}
	else
	{ //只是缺少物理页
		// vga_write_str_color("[Pte Fault!]",0x74);	//灰底红字
		(*((u32 *)K_PHY2LIN(pte_addr_phy_temp) + get_pte_index(cr2))) |= PG_P;
		// vga_write_str_color("[Solved]",0x74);
	}
	refresh_page_cache();
}

/***************************地址转换过程***************************
 *
 *第一步，CR3包含着页目录的起始地址，用32位线性地址的最高10位A31~A22作为页目录的页目录项的索引，
 *将它乘以4，与CR3中的页目录的起始地址相加，形成相应页表的地址。
 *
 *第二步，从指定的地址中取出32位页目录项，它的低12位为0，这32位是页表的起始地址。
 *用32位线性地址中的A21~A12位作为页表中的页面的索引，将它乘以4，与页表的起始地址相加，形成32位页面地址。
 *
 *第三步，将A11~A0作为相对于页面地址的偏移量，与32位页面地址相加，形成32位物理地址。
 *************************************************************************/

/*======================================================================*
			  get_pde_index		add by visual 2016.4.28
 *======================================================================*/
inline u32 get_pde_index(u32 AddrLin)
{				//由 线性地址 得到 页目录项编号
	return (AddrLin >> 22); //高10位A31~A22
}

/*======================================================================*
			  get_pte_index		add by visual 2016.4.28
 *======================================================================*/
 inline u32 get_pte_index(u32 AddrLin)
{					       //由 线性地址 得到 页表项编号
	return (((AddrLin)&0x003FFFFF) >> 12); //中间10位A21~A12,0x3FFFFF = 0000 0000 0011 1111 1111 1111 1111 1111
}

/*======================================================================*
			 get_pde_phy_addr	add by visual 2016.4.28
*======================================================================*/
inline u32 get_pde_phy_addr(u32 pid)
{ //获取页目录物理地址
	if (proc_table[pid].task.cr3 == 0)
	{ //还没有初始化页目录
		return -1;
	}
	else
	{
		return ((proc_table[pid].task.cr3) & 0xFFFFF000);
	}
}

/*======================================================================*
			 get_pte_phy_addr	add by visual 2016.4.28
*======================================================================*/
inline u32 get_pte_phy_addr(u32 pid,						    //页目录物理地址		//edit by visual 2016.5.19
				u32 AddrLin)						    //线性地址
{											    //获取该线性地址所属页表的物理地址
	u32 PageDirPhyAddr = get_pde_phy_addr(pid);					    // add by visual 2016.5.19
	return (*((u32 *)K_PHY2LIN(PageDirPhyAddr) + get_pde_index(AddrLin))) & 0xFFFFF000; //先找到该进程页目录首地址，然后计算出该线性地址对应的页目录项，再访问,最后注意4k对齐
}

/*======================================================================*
			 get_page_phy_addr	add by visual 2016.5.9
*======================================================================*/
inline u32 get_page_phy_addr(u32 pid,		     //页表物理地址				//edit by visual 2016.5.19
				u32 AddrLin)	     //线性地址
{							     //获取该线性地址对应的物理页物理地址
	u32 PageTblPhyAddr = get_pte_phy_addr(pid, AddrLin); // add by visual 2016.5.19
	return (*((u32 *)K_PHY2LIN(PageTblPhyAddr) + get_pte_index(AddrLin))) & 0xFFFFF000;
}

/*======================================================================*
			 pte_exist		add by visual 2016.4.28
*======================================================================*/
u32 pte_exist(u32 PageDirPhyAddr,							//页目录物理地址
		u32 AddrLin)								//线性地址
{												//判断 有没有 页表
	if ((0x00000001 & (*((u32 *)K_PHY2LIN(PageDirPhyAddr) + get_pde_index(AddrLin)))) == 0) //先找到该进程页目录,然后计算出该线性地址对应的页目录项,访问并判断其是否存在
	{											//标志位为0，不存在
		return 0;
	}
	else
	{
		return 1;
	}
}

/*======================================================================*
			phy_exist		add by visual 2016.4.28
*======================================================================*/
u32 phy_exist(u32 PageTblPhyAddr, //页表物理地址
		u32 AddrLin)	 //线性地址
{					 //判断 该线性地址 有没有 对应的 物理页
	if ((0x00000001 & (*((u32 *)K_PHY2LIN(PageTblPhyAddr) + get_pte_index(AddrLin)))) == 0)
	{ //标志位为0，不存在
		return 0;
	}
	else
	{
		return 1;
	}
}

/*======================================================================*
			 write_page_pde		add by visual 2016.4.28
*======================================================================*/
 void write_page_pde(u32 PageDirPhyAddr, //页目录物理地址
			   u32 AddrLin,	       //线性地址
			   u32 TblPhyAddr,     //要填写的页表的物理地址（函数会进行4k对齐）
			   u32 Attribute)      //属性
{					       //填写页目录
	(*((u32 *)K_PHY2LIN(PageDirPhyAddr) + get_pde_index(AddrLin))) = (TblPhyAddr & 0xFFFFF000) | Attribute;
	//进程页目录起始地址+每一项的大小*所属的项
}

/*======================================================================*
			  write_page_pte		add by visual 2016.4.28
 *======================================================================*/
void write_page_pte(u32 TblPhyAddr, //页表物理地址
			u32 AddrLin,	   //线性地址
			u32 PhyAddr,	   //要填写的物理页物理地址(任意的物理地址，函数会进行4k对齐)
			u32 Attribute)  //属性
{					   //填写页目录，会添加属性
	(*((u32 *)K_PHY2LIN(TblPhyAddr) + get_pte_index(AddrLin))) = (PhyAddr & 0xFFFFF000) | Attribute;
	//页表起始地址+一项的大小*所属的项
}

/*======================================================================*
 *                         vmalloc		add by visual 2016.5.4
 *从堆中分配size大小的内存，返回线性地址
 *======================================================================*/
u32 vmalloc(u32 size)
{
	u32 temp;
	if (p_proc_current->task.info.type == TYPE_PROCESS)
	{ //进程直接就是标识
		temp = p_proc_current->task.memmap.heap_lin_limit;
		p_proc_current->task.memmap.heap_lin_limit += size;
	}
	else
	{ //线程需要取父进程的标识
		temp = *((u32 *)p_proc_current->task.memmap.heap_lin_limit);
		(*((u32 *)p_proc_current->task.memmap.heap_lin_limit)) += size;
	}

	return temp;
}

/*======================================================================*
 *                          lin_mapping_phy		add by visual 2016.5.9
 *将线性地址映射到物理地址上去,函数内部会分配物理地址
 *======================================================================*/
int lin_mapping_phy(u32 AddrLin,	      //线性地址
			u32 phy_addr,      //物理地址,若为MAX_UNSIGNED_INT(0xFFFFFFFF)，则表示需要由该函数判断是否分配物理地址，否则将phy_addr直接和AddrLin建立映射
			u32 pid,	      //进程pid						//edit by visual 2016.5.19
			u32 pde_Attribute, //页目录中的属性位
			u32 pte_Attribute) //页表中的属性位
{
	u32 pte_addr_phy;
	u32 pde_addr_phy = get_pde_phy_addr(pid); // add by visual 2016.5.19

	if (0 == pte_exist(pde_addr_phy, AddrLin))
	{							    //页表不存在，创建一个，并填进页目录中
		pte_addr_phy = (u32)do_kmalloc_4k();		    //为页表申请一页
		memset((void *)K_PHY2LIN(pte_addr_phy), 0, num_4K); // add by visual 2016.5.26

		if (pte_addr_phy < 0 || (pte_addr_phy & 0x3FF) != 0) // add by visual 2016.5.9
		{
			vga_write_str_color("lin_mapping_phy Error:pte_addr_phy", 0x74);
			return -1;
		}

		write_page_pde(pde_addr_phy,   //页目录物理地址
			       AddrLin,	       //线性地址
			       pte_addr_phy,   //页表物理地址
			       pde_Attribute); //属性
	}
	else
	{						  //页表存在，获取该页表物理地址
		pte_addr_phy = get_pte_phy_addr(pid,	  //进程pid			//edit by visual 2016.5.19
						AddrLin); //线性地址
	}

	if (MAX_UNSIGNED_INT == phy_addr) // add by visual 2016.5.19
	{				  //由函数申请内存
		if (0 == phy_exist(pte_addr_phy, AddrLin))
		{ //无物理页，申请物理页并修改phy_addr
			if (AddrLin >= K_PHY2LIN(0))
				phy_addr = (u32)do_kmalloc_4k(); //从内核物理地址申请一页
			else
			{
				// kprintf("%");
				phy_addr = (u32)do_malloc_4k(); //从用户物理地址空间申请一页
			}
		}
		else
		{
			//有物理页，什么也不做,直接返回，必须返回
			return 0;
		}
	}
	else
	{ //指定填写phy_addr
	  //不用修改phy_addr
	}

	if (phy_addr < 0 || (phy_addr & 0x3FF) != 0)
	{
		vga_write_str_color("lin_mapping_phy:phy_addr ERROR", 0x74);
		return -1;
	}

	write_page_pte(pte_addr_phy,   //页表物理地址
		       AddrLin,	       //线性地址
		       phy_addr,       //物理页物理地址
		       pte_Attribute); //属性
	refresh_page_cache();

	return 0;
}

/*======================================================================*
 *                          clear_kernel_pagepte_low		add by visual 2016.5.12
 *将内核低端页表清除
 *======================================================================*/
void clear_kernel_pagepte_low()
{
	u32 page_num = *(u32 *)PageTblNumAddr;
	memset((void *)(K_PHY2LIN(KernelPageTblAddr)), 0, 4 * page_num);	     //从内核页目录中清除内核页目录项前8项
	memset((void *)(K_PHY2LIN(KernelPageTblAddr + 0x1000)), 0, 4096 * page_num); //从内核页表中清除线性地址的低端映射关系
	refresh_page_cache();
}
